var __createBinding =
    (this && this.__createBinding) ||
    (Object.create
        ? (o, m, k, k2) => {
              if (k2 === undefined) k2 = k;
              var desc = Object.getOwnPropertyDescriptor(m, k);
              if (
                  !desc ||
                  ("get" in desc
                      ? !m.__esModule
                      : desc.writable || desc.configurable)
              ) {
                  desc = { enumerable: true, get: () => m[k] };
              }
              Object.defineProperty(o, k2, desc);
          }
        : (o, m, k, k2) => {
              if (k2 === undefined) k2 = k;
              o[k2] = m[k];
          });
var __setModuleDefault =
    (this && this.__setModuleDefault) ||
    (Object.create
        ? (o, v) => {
              Object.defineProperty(o, "default", {
                  enumerable: true,
                  value: v,
              });
          }
        : (o, v) => {
              o.default = v;
          });
var __importStar =
    (this && this.__importStar) ||
    ((mod) => {
        if (mod?.__esModule) return mod;
        var result = {};
        if (mod != null)
            for (var k in mod)
                if (k !== "default" && Object.hasOwn(mod, k))
                    __createBinding(result, mod, k);
        __setModuleDefault(result, mod);
        return result;
    });
Object.defineProperty(exports, "__esModule", { value: true });
exports.rotateStringArray = void 0;
const t = __importStar(require("@babel/types"));
const expression_1 = require("../../expression");
const binaryOperatorSet = new Set(["+", "-", "*", "/", "%"]);
const unaryOperatorSet = new Set(["-"]);
const operationSet = new Set([
    "CallExpression",
    "UnaryExpression",
    "BinaryExpression",
    "NumericLiteral",
]);
/**
 * Parses an operation.
 * @param expression The expression.
 * @param decoderMap The string decoder map.
 * @returns The operation.
 */
function parseOperation(expression, decoderMap) {
    switch (expression.type) {
        case "CallExpression":
            return parseCallOperation(expression, decoderMap);
        case "UnaryExpression":
            return parseUnaryOperation(expression, decoderMap);
        case "BinaryExpression":
            return parseBinaryOperation(expression, decoderMap);
        case "NumericLiteral":
            return expression;
    }
}
/**
 * Parses a call operation.
 * @param expression The call expression.
 * @param decoderMap The string decoder map.
 * @returns The call operation.
 */
function parseCallOperation(expression, decoderMap) {
    if (
        !t.isIdentifier(expression.callee) ||
        expression.callee.name !== "parseInt" ||
        expression.arguments.length !== 1 ||
        !t.isCallExpression(expression.arguments[0])
    ) {
        throw new Error("Unsupported string call operation");
    }
    const stringCall = expression.arguments[0];
    if (
        !t.isIdentifier(stringCall.callee) ||
        !stringCall.arguments.every(
            (e) =>
                t.isNumericLiteral(e) ||
                (0, expression_1.isNegativeNumericLiteral)(e) ||
                t.isStringLiteral(e),
        )
    ) {
        throw new Error("Unsupported string call operation");
    }
    const args = stringCall.arguments.map((e) =>
        t.isNumericLiteral(e) || t.isStringLiteral(e)
            ? e.value
            : -e.argument.value,
    );
    const name = stringCall.callee.name;
    if (!decoderMap.has(name)) {
        throw new Error(`Unknown string decoder ${name}`);
    }
    const decoder = decoderMap.get(name);
    return {
        type: "CallOperation",
        decoder,
        args,
    };
}
/**
 * Parses a unary operation.
 * @param expression The unary expression.
 * @param decoderMap The string decoder map.
 * @returns The unary operation.
 */
function parseUnaryOperation(expression, decoderMap) {
    if (!unaryOperatorSet.has(expression.operator)) {
        throw new Error(`Unsupported unary operator ${expression.operator}`);
    } else if (!operationSet.has(expression.argument.type)) {
        throw new Error(
            `Unsupported string rotation operation type ${expression.argument.type}`,
        );
    }
    const argument = parseOperation(expression.argument, decoderMap);
    return {
        type: "UnaryOperation",
        operator: expression.operator,
        argument,
    };
}
/**
 * Parses a binary operation.
 * @param expression The binary expression.
 * @param decoderMap The string decoder map.
 * @returns The binary operation.
 */
function parseBinaryOperation(expression, decoderMap) {
    if (!binaryOperatorSet.has(expression.operator)) {
        throw new Error(`Unsupported binary operator ${expression.operator}`);
    } else if (!operationSet.has(expression.left.type)) {
        throw new Error(
            `Unsupported string rotation operation type ${expression.left.type}`,
        );
    } else if (!operationSet.has(expression.right.type)) {
        throw new Error(
            `Unsupported string rotation operation type ${expression.right.type}`,
        );
    }
    const left = parseOperation(expression.left, decoderMap);
    const right = parseOperation(expression.right, decoderMap);
    return {
        type: "BinaryOperation",
        operator: expression.operator,
        left,
        right,
    };
}
/**
 * Applies an operation.
 * @param operation The operation.
 * @returns The result.
 */
function applyOperation(operation) {
    switch (operation.type) {
        case "CallOperation":
            return applyCall(operation);
        case "UnaryOperation":
            return applyUnaryOperation(operation);
        case "BinaryOperation":
            return applyBinaryOperation(operation);
        case "NumericLiteral":
            return operation.value;
    }
}
/**
 * Applies a call of a string decoder.
 * @param call The call.
 * @returns The result.
 */
function applyCall(call) {
    return parseInt(call.decoder.getStringForRotation(...call.args), 10);
}
/**
 * Applies a unary operation.
 * @param operation The unary operation.
 * @returns The result.
 */
function applyUnaryOperation(operation) {
    const argument = applyOperation(operation.argument);
    switch (operation.operator) {
        case "-":
            return -argument;
    }
}
/**
 * Applies a binary operation.
 * @param operation The binary operation.
 * @returns The result.
 */
function applyBinaryOperation(operation) {
    const left = applyOperation(operation.left);
    const right = applyOperation(operation.right);
    switch (operation.operator) {
        case "+":
            return left + right;
        case "-":
            return left - right;
        case "*":
            return left * right;
        case "/":
            return left / right;
        case "%":
            return left % right;
    }
}
/**
 * Rotates the string array.
 * @param expression The expression containing the string array calls.
 * @param decoderMap The string decoder map.
 * @param stopValue The value to stop at.
 */
function rotateStringArray(array, expression, decoderMap, stopValue) {
    const operation = parseOperation(expression, decoderMap);
    let i = 0;
    while (true) {
        try {
            const value = applyOperation(operation);
            if (value === stopValue) {
                break;
            } else {
                array.push(array.shift());
            }
        } catch (_err) {
            array.push(array.shift());
        }
        // avoid entering infinite loops
        if (i++ > 1e5) {
            throw new Error("Max number of string rotation iterations reached");
        }
    }
}
exports.rotateStringArray = rotateStringArray;
